Skip to content

Schedules hotfix for DTS dashboard#169

Merged
andystaples merged 2 commits into
microsoft:mainfrom
andystaples:andystaples/fix-schedules-dts-dashboard
Jul 8, 2026
Merged

Schedules hotfix for DTS dashboard#169
andystaples merged 2 commits into
microsoft:mainfrom
andystaples:andystaples/fix-schedules-dts-dashboard

Conversation

@andystaples

@andystaples andystaples commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Schedules created with durabletask.scheduled were failing to load in the Durable Task Scheduler (DTS) dashboard. The dashboard backend reads the raw schedule entity state and deserializes it into .NET types with System.Text.Json (Web defaults), but the Python SDK persisted the state in a Python-native shape.

This PR aligns the persisted schedule entity state (ScheduleState / ScheduleConfiguration) with the .NET SDK wire format so the dashboard can read it. In-memory representations are unchanged; only the persisted JSON shape is adjusted (with tolerant reads for round-tripping).

Fixes

  • status as a numeric enum, not a string. System.Text.Json (Web defaults) has no string-enum converter, so it expects the ordinal. ScheduleStatus is now serialized as its .NET ordinal (Uninitialized=0, Active=1, Paused=2). Fixes the original could not be converted to ... ScheduleStatus. Path: $.status error.
  • interval as a .NET TimeSpan string, not interval_seconds. The interval is now formatted/parsed as [-][d.]hh:mm:ss[.fffffff] (e.g. 01:00:00) to match the .NET TimeSpan JSON converter.
  • OrchestrationInput as a JSON-encoded string, not a raw object. .NET types the field as string?; Python held it as Any. A dict/object input serialized to a JSON object and was rejected (Cannot get the value of a token type 'StartObject' as a string. Path: $.ScheduleConfiguration.OrchestrationInput). It is now serialized to a JSON string on persist and decoded back on read; the in-memory value stays a raw object so the orchestration still starts with the correct input.
  • PascalCase property names throughout. ScheduleState and ScheduleConfiguration now emit .NET property names (Status, ExecutionToken, ScheduleConfiguration, ScheduleId, Interval, etc.) so case-insensitive matching in the backend succeeds (snake_case underscores previously broke the match).

Backward compatibility

  • from_json on both types tolerates the legacy snake_case/string shapes (numeric or string status, interval_seconds, raw input) so states persisted by earlier Python workers still round-trip.

Review feedback addressed

  • _from_iso only normalizes a trailing Z (no longer replaces every Z in the string).
  • ScheduleStatus.from_dotnet uses a PEP 604 union (int | str | None); removed the now-unused typing.Union import.
  • ScheduleState.from_json preserves the __init__-generated execution_token when the field is absent, instead of overwriting it with None (which would make every run_schedule signal look stale and silently stop the schedule).

Testing

  • Added regression tests for the .NET-compatible JSON shape (numeric status, TimeSpan interval, JSON-string input), legacy-format deserialization, and execution-token preservation.
  • All tests/durabletask/scheduled/ tests pass; flake8 clean.
  • Verified end-to-end against the DTS emulator: created a schedule with a dictionary orchestration_input (the exact shape that previously failed) and confirmed the dashboard's GET /api/v1/taskhubs/schedules endpoint deserializes and lists it without error.

Copilot AI review requested due to automatic review settings July 6, 2026 20:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the durabletask.scheduled persisted schedule entity JSON shape to match what the Durable Task Scheduler (DTS) dashboard expects when deserializing state as .NET types.

Changes:

  • Serialize ScheduleConfiguration using PascalCase property names and format interval as a .NET TimeSpan string.
  • Serialize ScheduleState.status as the .NET enum ordinal (int) and accept legacy string/snake_case formats when reading.
  • Add regression tests for the .NET-compatible JSON shape and update the changelog with the user-visible fix.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
tests/durabletask/scheduled/test_models.py Adds tests validating .NET-compatible JSON serialization and legacy deserialization support.
durabletask/scheduled/schedule_status.py Adds helpers to convert schedule status to/from .NET ordinal/string representations.
durabletask/scheduled/models.py Implements PascalCase JSON shape, TimeSpan interval formatting/parsing, and more tolerant deserialization.
CHANGELOG.md Documents the dashboard compatibility fix under ## Unreleased.

Comment thread durabletask/scheduled/models.py Outdated
Comment thread durabletask/scheduled/schedule_status.py Outdated
Comment thread durabletask/scheduled/schedule_status.py
Comment thread durabletask/scheduled/models.py Outdated
Persist OrchestrationInput as a JSON-encoded string so the DTS dashboard can deserialize it (it types the field as string?). Also address Copilot review feedback: restrict _from_iso Z-normalization to a trailing Z, use PEP 604 unions in ScheduleStatus.from_dotnet, and preserve the generated execution_token when absent from persisted state.
@andystaples

Copy link
Copy Markdown
Contributor Author

Verified in the live DTS dashboard:
image

@berndverst berndverst left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the persisted-state realignment against the .NET wire format — this looks correct and well-tested. Approving.

Verified the cross-language claims:

  • .NET ScheduleStatus enum order confirmed against microsoft/durabletask-dotnet (src/ScheduledTasks/Models/ScheduleStatus.cs): Uninitialized=0, Active=1, Paused=2, matching _STATUS_TO_ORDINAL exactly.
  • TimeSpan format/parse is correct, including the day component (1.02:03:04) and 7-digit fractional ticks (00:00:01.5000000).
  • OrchestrationInput as a JSON-encoded string matches .NET's string? typing (consistent with the quoted StartObject-as-string error); PascalCase keys satisfy System.Text.Json case-insensitive matching.
  • Client read path (get_typed_stateScheduleState.from_json) and the legacy snake_case reads round-trip. durabletask-azuremanaged inherits the core types with no serialization override, so nothing to update there.

CI is green across all Python versions and the review feedback (trailing-Z normalization, PEP 604 union, execution-token preservation) is all addressed. Tests are thorough.

Non-blocking concerns to keep in mind:

  1. No forward compatibility. New workers persist the .NET-shaped state; an old (pre-PR) worker reading that state breaks hard — ScheduleStatus(1) raises ValueError on the str-enum, and the missing interval_seconds raises KeyError. In a mixed-version fleet during rollout, schedules touched by an upgraded worker would fail on older workers still in the pool. Acceptable for a hotfix, but worth upgrading the fleet together and/or noting in the release notes.

  2. Minor: schedule orchestration_input is encoded/decoded via a hardcoded default JsonDataConverter, bypassing any custom worker-configured converter. Not a regression (inputs already degraded to JSON-native across a persistence round-trip, and the actual orchestration start still uses the worker's converter), but schedule inputs must be plain-JSON-serializable by the default converter. Possible follow-up if custom-typed schedule inputs are a scenario you want to support.

@berndverst

Copy link
Copy Markdown
Member

Could it be that the Dashboard needs to be more flexible instead?

This isn't really a fix required in the Python SDK is it?

@berndverst berndverst self-requested a review July 8, 2026 22:09
@andystaples andystaples merged commit 652e58b into microsoft:main Jul 8, 2026
17 checks passed
@andystaples andystaples deleted the andystaples/fix-schedules-dts-dashboard branch July 8, 2026 22:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants